#include "opencv2/opencv.hpp"

using namespace cv;
using namespace std;

namespace {

const string cascadeName = "../../../../data/haarcascade_frontalface_alt.xml";
const string nestedCascadeName =
    "../../../../data/haarcascade_eye_tree_eyeglasses.xml";
const static Scalar colors[] = {Scalar(255, 0, 0),   Scalar(255, 128, 0),
                                Scalar(255, 255, 0), Scalar(0, 255, 0),
                                Scalar(0, 128, 255), Scalar(0, 255, 255),
                                Scalar(0, 0, 255),   Scalar(255, 0, 255)};
}  // namespace

void detectAndDraw(Mat& img, CascadeClassifier& cascade,
                   CascadeClassifier& nestedCascade, double scale,
                   bool tryflip);

int main() {
  VideoCapture capture(0);
  Mat frame, image;
  string inputName;
  CascadeClassifier cascade, nestedCascade;
  if (!nestedCascade.load(nestedCascadeName))
    cerr << "WARNING: Could not load classifier cascade for nested objects"
         << endl;
  if (!cascade.load(cascadeName)) {
    cerr << "ERROR: Could not load classifier cascade" << endl;
    return -1;
  }

  bool tryflip = false;
  double scale = 1.;
  if (capture.isOpened()) {
    cout << "Video capturing has been started ..." << endl;

    for (;;) {
      capture >> frame;
      if (frame.empty()) break;

      Mat frame1 = frame.clone();
      detectAndDraw(frame1, cascade, nestedCascade, scale, tryflip);

      char c = (char)waitKey(10);
      if (c == 27 || c == 'q' || c == 'Q') break;
    }
  } else {
    cout << "Detecting face(s) in " << inputName << endl;
    if (!image.empty()) {
      detectAndDraw(image, cascade, nestedCascade, scale, tryflip);
      waitKey(0);
    } else if (!inputName.empty()) {
      /* assume it is a text file containing the
      list of the image filenames to be processed - one per line */
      FILE* f = fopen(inputName.c_str(), "rt");
      if (f) {
        char buf[1000 + 1];
        while (fgets(buf, 1000, f)) {
          int len = (int)strlen(buf);
          while (len > 0 && isspace(buf[len - 1])) len--;
          buf[len] = '\0';
          cout << "file " << buf << endl;
          image = imread(buf, 1);
          if (!image.empty()) {
            detectAndDraw(image, cascade, nestedCascade, scale, tryflip);
            char c = (char)waitKey(0);
            if (c == 27 || c == 'q' || c == 'Q') break;
          } else {
            cerr << "Aw snap, couldn't read image " << buf << endl;
          }
        }
        fclose(f);
      }
    }
  }

  return 0;
}

void detectAndDraw(Mat& img, CascadeClassifier& cascade,
                   CascadeClassifier& nestedCascade, double scale,
                   bool tryflip) {
  vector<Rect> faces, faces2;

  Mat gray, smallImg;

  cvtColor(img, gray, COLOR_BGR2GRAY);
  double fx = 1 / scale;
  resize(gray, smallImg, Size(), fx, fx, INTER_LINEAR_EXACT);
  // equalizeHist(smallImg, smallImg);

  cascade.detectMultiScale(smallImg, faces, 1.1, 2, 0 | CASCADE_SCALE_IMAGE,
                           Size(30, 30));
  if (tryflip) {
    flip(smallImg, smallImg, 1);
    cascade.detectMultiScale(smallImg, faces2, 1.1, 2, 0 | CASCADE_SCALE_IMAGE,
                             Size(30, 30));
    for (vector<Rect>::const_iterator r = faces2.begin(); r != faces2.end();
         ++r) {
      faces.push_back(
          Rect(smallImg.cols - r->x - r->width, r->y, r->width, r->height));
    }
  }

  for (size_t i = 0; i < faces.size(); i++) {
    Rect r = faces[i];
    Mat smallImgROI;
    vector<Rect> nestedObjects;
    Point center;
    Scalar color = colors[i % 8];
    int radius;

    double aspect_ratio = (double)r.width / r.height;
    if (0.75 < aspect_ratio && aspect_ratio < 1.3) {
      center.x = cvRound((r.x + r.width * 0.5) * scale);
      center.y = cvRound((r.y + r.height * 0.5) * scale);
      radius = cvRound((r.width + r.height) * 0.25 * scale);
      circle(img, center, radius, color, 3, 8, 0);
    } else
      rectangle(img, Point(cvRound(r.x * scale), cvRound(r.y * scale)),
                Point(cvRound((r.x + r.width - 1) * scale),
                      cvRound((r.y + r.height - 1) * scale)),
                color, 3, 8, 0);
    if (nestedCascade.empty()) continue;
    smallImgROI = smallImg(r);
    nestedCascade.detectMultiScale(smallImgROI, nestedObjects, 1.1, 2,
                                   0 | CASCADE_SCALE_IMAGE, Size(30, 30));
    for (size_t j = 0; j < nestedObjects.size(); j++) {
      Rect nr = nestedObjects[j];
      center.x = cvRound((r.x + nr.x + nr.width * 0.5) * scale);
      center.y = cvRound((r.y + nr.y + nr.height * 0.5) * scale);
      radius = cvRound((nr.width + nr.height) * 0.25 * scale);
      circle(img, center, radius, color, 3, 8, 0);
    }
  }
  imshow("result", img);
}
